/* Actual NTP implementation */

/* Copyright (C) 2009-2012 David Zanetti
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; version 2 of the License.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */


#include <stdio.h>
#include <stdlib.h>
#include <avr/io.h>
#include "global.h"
#include "ether.h"
#include "ip.h"
#include <string.h>
#include "checksum.h"
#include "encx24j600.h"
#include <util/delay.h>
#include "serial.h"
#include <avr/pgmspace.h>
#include "udp.h"
#include "ntp.h"
#include <avr/interrupt.h>
#include <util/atomic.h>
#include "ds3234.h"
#include "gps.h"

typedef struct {
		uint8_t li_ver_mode;
		uint8_t stratum;
		uint8_t poll;
		int8_t precision;
		uint8_t root_delay[4];
		uint8_t root_dispersion[4];
		uint8_t ref_id[4];
		uint8_t ref_time[8];
		uint8_t orig_time[8];
		uint8_t rx_time[8];
		uint8_t tx_time[8];
} ntp_t;

/* process incoming NTP packets, using slot given for reply */
uint8_t ntp_process(uint8_t slot) {
	ntp_t pkt, out;
	uint32_t rx_sec;
	uint16_t rx_ms;
	uint32_t ntp_s; /* used in converting to NTP format */
	uint32_t ntp_frac;

	/* save date and time of receive, so we can correctly report our
	 * processesing delay */
	ds3234_now_ntp(&rx_sec,&rx_ms);
//	rx_sec = (((uint32_t)ds3234_msb_s << 16) | ((uint32_t)TCE0.CNT)) - gps_leap;
//	rx_ms = TCD0.CNT & 0x7fff;
//	/* ugly fudge */
//#ifdef GPS_FUDGE
//        rx_ms = (rx_ms - GPS_FUDGE);
//	if (rx_ms > 0x7fff) {
//         rx_sec--;
//	 rx_ms = rx_ms & 0x7fff;
//        }
//#endif

	/* read the packet */
	if (encx24j600_rx(&pkt, sizeof(ntp_t)) != sizeof(ntp_t)) {
		serial0_tx_PGM(PSTR("ntp: short frame, discarding\r\n"));
		return E_NOTHING;
	}

#ifdef DEBUG_NTP
	serial0_tx_PGM(PSTR("ntp: -> ver "));
	serial0_tx_dec((pkt.li_ver_mode & 0x38) >> 3);
	serial0_tx_PGM(PSTR(" mode "));
	serial0_tx_dec(pkt.li_ver_mode & 0x7);
	serial0_tx_cr();
#endif

	/* set leap indication (don't know!), version, and mode */
	if (flag_clock & FLAG_CLOCK_SYNC) {
		/* FIXME: add leap second indication as discovered by querying GPS */
		out.li_ver_mode = 0x24; // 0x24 = V4, server, no leap second warning
	} else {
		out.li_ver_mode = 0xE4; /* li = 11, no sync */
	}

	if (flag_clock & FLAG_CLOCK_SYNC) {
		out.stratum = 1; /* we are a very low stratum server */
	} else {
		out.stratum = 0; /* unsynced */
	}
	out.precision = -15;
	out.poll = pkt.poll; /* in SNTP we just reflect it back */

	/* since we are stratum 1, no root delay */
	out.root_delay[0] = 0;
	out.root_delay[1] = 0;
	out.root_delay[2] = 0;
	out.root_delay[3] = 0x0;

	/* how much it our root drifts, very minimal */
	out.root_dispersion[0] = 0;
	out.root_dispersion[1] = 0;
	out.root_dispersion[2] = 0;
	out.root_dispersion[3] = 0x2;

	/* reference source */
	if (flag_clock & FLAG_CLOCK_SYNC) {
		/* our reference clock id is "GPS" */
		out.ref_id[0] = 'G';
		out.ref_id[1] = 'P';
		out.ref_id[2] = 'S';
		out.ref_id[3] = 0;
	} else {
		out.ref_id[0] = 0;
		out.ref_id[1] = 0;
		out.ref_id[2] = 0;
		out.ref_id[3] = 0;
	}

	/* reference time is the last time we inserted ticks or synced */

	/* ref seconds is easy */
	//ntp_s = (uint32_t) ds3234_reference_s + 2208988800UL;
	ntp_s = ds3234_reference_s; /* already converted */
	/* FIXME: SNTP suggests random low-order bits */
	ntp_frac = (uint32_t) ds3234_reference_ms << 17;
	//ntp_frac = ((uint32_t) ds3234_reference_ms << 17) | ((uint32_t) (ds3234_reference_rand & 0x1ff) << 8) | ((uint32_t) ds3234_reference_rand & 0xff);

	/* now store it */
	out.ref_time[0] = (ntp_s >> 24);
	out.ref_time[1] = (ntp_s >> 16);
	out.ref_time[2] = (ntp_s >> 8);
	out.ref_time[3] = (ntp_s & 0xff);
	out.ref_time[4] = (ntp_frac >> 24);
	out.ref_time[5] = (ntp_frac >> 16);
	out.ref_time[6] = (ntp_frac >> 8);
	out.ref_time[7] = (ntp_frac & 0xff);

	/* originate we can just copy as-is from incoming TX time */
	memcpy(&(out.orig_time),&(pkt.tx_time),8);

	/* RX time on the packet */
	/* FIXME: log the RX time earlier than this */

	/* rx seconds is easy */
	//ntp_s = (uint32_t) rx_sec + (uint32_t) 2208988800;
	ntp_s = rx_sec;
	/* the rand bits here are required by NTP spec */
	/* FIXME: SNTP doesn't require, but does suggest, random bits here */
	ntp_frac = (uint32_t) rx_ms << 17;
	//ntp_frac = ((uint32_t) rx_ms << 17) | ((uint32_t) (rand() & 0x1ff) << 8) | ((uint32_t) rand() & 0xff);

	/* now store it */
	out.rx_time[0] = (ntp_s >> 24);
	out.rx_time[1] = (ntp_s >> 16);
	out.rx_time[2] = (ntp_s >> 8);
	out.rx_time[3] = (ntp_s & 0xff);
	out.rx_time[4] = (ntp_frac >> 24);
	out.rx_time[5] = (ntp_frac >> 16);
	out.rx_time[6] = (ntp_frac >> 8);
	out.rx_time[7] = (ntp_frac & 0xff);

	/* and TX time is just now, overwrite the RX bits so we get a clean copy */
	ds3234_now_ntp(&rx_sec,&rx_ms);
//	rx_sec = ((uint32_t)ds3234_msb_s << 16) | ((uint32_t)TCE0.CNT);
//	rx_ms = TCD0.CNT & 0x7fff;
//#ifdef GPS_FUDGE
//	/* this is an ugly fudge */
//        rx_ms = (rx_ms - GPS_FUDGE);
//        if (rx_ms > 0x7fff) {
//         rx_sec--;
//         rx_ms = rx_ms & 0x7fff;
//        }
//#endif

	/* tx seconds is easy */
	//ntp_s = (uint32_t) rx_sec + (uint32_t) 2208988800;
	ntp_s = rx_sec;
	/* FIXME: SNTP suggests that low order bits be random */
	ntp_frac = (uint32_t) rx_ms << 17;
	//ntp_frac = ((uint32_t) rx_ms << 17) | ((uint32_t) (rand() & 0x1ff) << 8) | ((uint32_t) rand() & 0xff);

	/* now store it */
	out.tx_time[0] = (ntp_s >> 24);
	out.tx_time[1] = (ntp_s >> 16);
	out.tx_time[2] = (ntp_s >> 8);
	out.tx_time[3] = (ntp_s & 0xff);
	out.tx_time[4] = (ntp_frac >> 24);
	out.tx_time[5] = (ntp_frac >> 16);
	out.tx_time[6] = (ntp_frac >> 8);
	out.tx_time[7] = (ntp_frac & 0xff);

	/* and now send it */
	if (!encx24j600_tx_prepend(slot,(uint8_t *)&out,sizeof(ntp_t))) {
		serial0_tx_PGM(PSTR("ntp: failed to write outbound packet\r\n"));
		return E_NOTHING;
	}

#ifdef DEBUG_NTP
	serial0_tx_PGM(PSTR("ntp: tx reply\r\n"));
#endif

	return E_REPLY;
} 



